package com.gitee.wsl.net.ssdp.description.model

import com.gitee.wsl.net.ssdp.description.model.xml.ActionList
import com.gitee.wsl.net.ssdp.description.model.xml.ArgumentList
import com.gitee.wsl.net.ssdp.description.model.xml.DeviceDescription
import com.gitee.wsl.net.ssdp.description.model.xml.IconList
import com.gitee.wsl.net.ssdp.description.model.xml.ServiceList
import com.gitee.wsl.net.ssdp.description.model.xml.ServiceStateTable
import com.gitee.wsl.net.ssdp.description.model.xml.SpecVersion
import com.gitee.wsl.net.ssdp.description.model.xml.SystemVersion
import com.gitee.wsl.net.ssdp.description.model.xml.XmlServiceDescription
import io.ktor.http.*
import java.util.Locale


data class DeviceManifest(
    val usn: String,
    val metadata: Metadata,
    val rootDevice: Device
) {
    data class Metadata(
        val language: Locale = Locale.ENGLISH,
        val location: Url,
        val urns: Map<String, String> = emptyMap(),
        val specVersion: SpecVersion? = null,
        val systemVersion: SystemVersion? = null,
        val configId: Int? = null,
        val error: String? = null
    )

    data class Device(
        val deviceType: String,
        val friendlyName: String,
        val manufacturer: String,
        val manufacturerUrl: Url? = null,
        val modelDescription: String? = null,
        val modelName: String,
        val modelNumber: String? = null,
        val modelUrl: Url? = null,
        val serialNumber: String? = null,
        val udn: String,
        val originUdn: String? = null,
        val upc: String? = null,
        val presentationUrl: Url,
        val icons: List<Icon>,
        var services: List<Service>,
        val devices: List<Device>
    ) {
        companion object {
            fun fromDescription(descr: DeviceDescription, baseUrl: URLBuilder): Device = Device(
                deviceType = descr.deviceType,
                friendlyName = descr.friendlyName,
                manufacturer = descr.manufacturer,
                manufacturerUrl = descr.manufacturerUrl?.let(::Url),
                modelDescription = descr.modelDescription,
                modelName = descr.modelName,
                modelNumber = descr.modelNumber,
                modelUrl = descr.modelUrl?.let(::Url),
                serialNumber = descr.serialNumber,
                udn = descr.udn,
                originUdn = descr.originUdn,
                upc = descr.upc,
                icons = descr.iconList?.map { Icon.fromDescription(it, baseUrl) } ?: emptyList(),
                services = descr.serviceList?.map { Service.fromDescription(it, baseUrl) } ?: emptyList(),
                devices = descr.deviceList?.map { fromDescription(it, baseUrl) } ?: emptyList(),
                presentationUrl = baseUrl.build()
            )
        }
    }

    data class Icon(
        val mimetype: String,
        val width: Int,
        val height: Int,
        val depth: Int,
        val url: Url
    ) {
        companion object {
            fun fromDescription(descr: IconList.Icon, baseUrl: URLBuilder) = Icon(
                descr.mimetype,
                descr.width,
                descr.height,
                descr.depth,
                baseUrl.also { it.path(descr.url) }.build()
            )
        }
    }

    data class Service(
        var metadata: Metadata? = null,
        val serviceType: String,
        val serviceId: String,
        val scpdUrl: Url,
        val controlUrl: Url,
        val eventSubUrl: Url,
        var actionList: List<Action>? = null,
        var serviceStateTable: List<StateVariable>? = null
    ) {
        data class Action(
            val name: String,
            val arguments: List<Argument>
        ) {
            data class Argument(
                val name: String,
                val direction: String,
                val returnValue: String? = null,
                val relatedStateVariable: String
            ) {
                companion object {
                    fun fromDescription(descr: ArgumentList.Argument) = Argument(
                        descr.name,
                        descr.direction,
                        descr.retval,
                        descr.relatedStateVariable
                    )
                }
            }

            companion object {
                fun fromDescription(descr: ActionList.Action) = Action(
                    descr.name,
                    descr.argumentList?.map { Argument.fromDescription(it) } ?: emptyList()
                )
            }
        }

        data class StateVariable(
            val name: String,
            val dataType: String,
            val defaultValue: String? = null,
            val allowedValueList: List<String>? = null,
            val allowedValueRange: ValueRange? = null,
            val sendEvents: Boolean? = null,
            val isMulticast: Boolean? = null
        ) {
            data class ValueRange(
                val minimum: String,
                val maximum: String,
                val step: String? = null
            ) {
                companion object {
                    fun fromDescription(descr: com.gitee.wsl.net.ssdp.description.model.xml.ValueRange) = ValueRange(
                        descr.minimum,
                        descr.maximum,
                        descr.step
                    )
                }
            }

            companion object {
                fun fromDescription(descr: ServiceStateTable.StateVariable) = StateVariable(
                    descr.name,
                    descr.dataType,
                    descr.defaultValue,
                    descr.allowedValueList,
                    descr.allowedValueRange?.let(ValueRange::fromDescription),
                    descr.sendEvents == "yes",
                    descr.multicast == "yes"
                )
            }
        }

        fun extendWith(description: XmlServiceDescription, location: Url): Service {
            metadata = Metadata(
                language = Locale.forLanguageTag(description.language),
                location = location,
                urns = description.urns,
                specVersion = description.specVersion,
                configId = description.configId
            )
            actionList = description.actionList?.map { Action.fromDescription(it) }
            serviceStateTable = description.serviceStateTable.map { StateVariable.fromDescription(it) }
            return this
        }

        fun recordError(error: String, location: Url): Service {
            metadata = Metadata(location=location, error = error)
            return this
        }

        companion object {
            fun fromDescription(
                serviceDescription: ServiceList.Service,
                baseUrl: URLBuilder
            ) = Service(
                serviceType = serviceDescription.serviceType,
                serviceId = serviceDescription.serviceId,
                scpdUrl = baseUrl.also { it.path(serviceDescription.scpdUrl) }.build(),
                controlUrl = baseUrl.also { it.path(serviceDescription.controlUrl) }.build(),
                eventSubUrl = baseUrl.also { it.path(serviceDescription.eventSubUrl) }.build(),
            )
        }
    }
}